package com.michaldrabik.tapbarmenulib;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.ElementScatter;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.multimodalinput.event.TouchEvent;

public class TapBarMenu extends DirectionalLayout implements Component.BindStateChangedListener, Component.DrawTask, Component.TouchEventListener {

  static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00901, "PIKACHU");

  public static final int BUTTON_POSITION_LEFT = 0;
  public static final int BUTTON_POSITION_CENTER = 1;
  public static final int BUTTON_POSITION_RIGHT = 2;
  public static final int MENU_ANCHOR_BOTTOM = 3;
  public static final int MENU_ANCHOR_TOP = 4;

  private static final int LEFT = 0;
  private static final int RIGHT = 1;
  private static final int TOP = 2;
  private static final int BOTTOM = 3;
  private static final int RADIUS = 4;
  private final AnimatorValue animatorValue = new AnimatorValue();
  private final ValueAnimator[] animator = new ValueAnimator[5];
  private float progress = 0;
  private final float[] button = new float[5];
  private final Path path = new Path();
  private State state = State.CLOSED;
  private Paint paint;
  private int animationDuration;
  private float width;
  private float height;
  private float yPosition;
  private Element iconOpenedDrawable;
  private Element iconClosedDrawable;
  private ClickedListener onClickListener;

  //Custom XML Attributes
  private Color backgroundColor;
  private int buttonSize;
  private int buttonPosition;
  private int buttonMarginRight;
  private int buttonMarginLeft;
  private int menuAnchor;
  private boolean showMenuItems;

  public TapBarMenu(Context context) {
    super(context);
  }

  public TapBarMenu(Context context, AttrSet attrSet) {
    super(context, attrSet);
    init(attrSet);
  }

  public TapBarMenu(Context context, AttrSet attrSet, String styleName) {
    super(context, attrSet, styleName);
    init(attrSet);
  }

  private void init(AttrSet attrs) {
    setupAttributes(attrs);
    setAlignment(LayoutAlignment.CENTER);
    setupAnimators();
    setupPaint();

    setBindStateChangedListener(this);
    addDrawTask(this, BETWEEN_BACKGROUND_AND_CONTENT);
    setTouchEventListener(this);
  }

  private void setupAttributes(AttrSet attrs) {

    if (attrs.getAttr("tbm_iconOpened").isPresent()) {
      iconOpenedDrawable = attrs.getAttr("tbm_iconOpened").get().getElement();
    } else {
      iconOpenedDrawable = ElementScatter.getInstance(getContext()).parse(ResourceTable.Graphic_close);
    }

    if (attrs.getAttr("tbm_iconClosed").isPresent()) {
      iconOpenedDrawable = attrs.getAttr("tbm_iconClosed").get().getElement();
    } else {
      iconClosedDrawable = ElementScatter.getInstance(getContext()).parse(ResourceTable.Graphic_menu);
    }

    backgroundColor = attrs.getAttr("tbm_backgroundColor").isPresent() ? attrs.getAttr("tbm_backgroundColor").get().getColorValue() : new Color(0xffe55452);
    buttonSize = attrs.getAttr("tbm_buttonSize").isPresent() ? attrs.getAttr("tbm_buttonSize").get().getDimensionValue() : 168;
    buttonMarginRight = attrs.getAttr("tbm_buttonMarginRight").isPresent() ? attrs.getAttr("tbm_buttonMarginRight").get().getDimensionValue() : 0;
    buttonMarginLeft = attrs.getAttr("tbm_buttonMarginLeft").isPresent() ? attrs.getAttr("tbm_buttonMarginLeft").get().getDimensionValue() : 0;
    buttonPosition = attrs.getAttr("tbm_buttonPosition").isPresent() ? attrs.getAttr("tbm_buttonPosition").get().getIntegerValue() : BUTTON_POSITION_CENTER;
    menuAnchor = attrs.getAttr("tbm_menuAnchor").isPresent() ? attrs.getAttr("tbm_menuAnchor").get().getIntegerValue() : MENU_ANCHOR_BOTTOM;
    showMenuItems = attrs.getAttr("tbm_showItems").isPresent() && attrs.getAttr("tbm_showItems").get().getBoolValue();

    animationDuration = 250;
  }

  private void setupAnimators() {
    for (int i = 0; i < 5; i++) {
      animator[i] = new ValueAnimator();
    }
    animatorValue.setDuration(animationDuration);
    animatorValue.setCurveType(Animator.CurveType.CUBIC_BEZIER_EXTREME_DECELERATION);
    animatorValue.setValueUpdateListener((AnimatorValue animatorValue, float v) -> {
      progress = v;
      button[LEFT] = animator[LEFT].getValue(v);
      button[TOP] = animator[TOP].getValue(v);
      button[RIGHT] = animator[RIGHT].getValue(v);
      button[BOTTOM] = animator[BOTTOM].getValue(v);
      button[RADIUS] = animator[RADIUS].getValue(v);
      addDrawTask(this, BETWEEN_BACKGROUND_AND_CONTENT);
    });
  }

  private void setupMenuItems() {
    for (int i = 0; i < getChildCount(); i++) {
      getComponentAt(i).setVisibility(showMenuItems ? VISIBLE : HIDE);
    }
  }

  private void setupPaint() {
    paint = new Paint();
    paint.setColor(backgroundColor);
    paint.setAntiAlias(true);
  }

  @Override
  public void onComponentBoundToWindow(Component component) {
    setupMenuItems();
    updateDimensions(getEstimatedWidth(), getEstimatedHeight());
  }

  /**
   * Opens the menu if it's closed or close it if it's opened.
   */
  public void toggle() {
    if (state == State.OPENED) {
      close();
    } else {
      open();
    }
  }

  /**
   * Open the menu.
   */
  public void open() {
    state = State.OPENED;
    showIcons(true);

    updateDimensions(getEstimatedWidth(), getEstimatedHeight());

    animator[LEFT].setFloatValues(button[LEFT], 0);
    animator[TOP].setFloatValues(button[TOP], 0);
    animator[RIGHT].setFloatValues(button[RIGHT], width);
    animator[BOTTOM].setFloatValues(button[BOTTOM], height);
    animator[RADIUS].setFloatValues(button[RADIUS], 0);

    animatorValue.cancel();
    animatorValue.start();

    HiLog.info(TapBarMenu.label, "--> %{public}f", this.getContentPositionY());

    yPosition = getContentPositionY();

    ComponentContainer container = (ComponentContainer) (this.getComponentParent());
    this.createAnimatorProperty()
        .moveFromY(yPosition)
        .moveToY(menuAnchor == MENU_ANCHOR_BOTTOM ? container.getEstimatedHeight() - getEstimatedHeight() : 0)
        .setDuration(animationDuration)
        .setCurveType(Animator.CurveType.CUBIC_BEZIER_EXTREME_DECELERATION)
        .start();
  }

  /**
   * Close the menu.
   */
  public void close() {
    state = State.CLOSED;
    showIcons(false);

    updateDimensions(getEstimatedWidth(), getEstimatedHeight());

    animator[LEFT].setFloatValues(0, button[LEFT]);
    animator[TOP].setFloatValues(0, button[TOP]);
    animator[RIGHT].setFloatValues(width, button[RIGHT]);
    animator[BOTTOM].setFloatValues(height, button[BOTTOM]);
    animator[RADIUS].setFloatValues(0, button[RADIUS]);

    animatorValue.cancel();
    animatorValue.start();

    ComponentContainer container = (ComponentContainer) (this.getComponentParent());
    this.createAnimatorProperty()
        .moveFromY(menuAnchor == MENU_ANCHOR_BOTTOM ? container.getEstimatedHeight() - getEstimatedHeight() : 0)
        .moveToY(yPosition)
        .setDuration(animationDuration)
        .setCurveType(Animator.CurveType.CUBIC_BEZIER_EXTREME_DECELERATION)
        .start();
  }

  /**
   * @return True if menu is opened. False otherwise.
   */
  public boolean isOpened() {
    return state == State.OPENED;
  }

  /**
   * Sets TapBarMenu's background color from given resource.
   *
   * @param colorResId Color resource id. For example: R.color.holo_blue_light
   */
  public void setMenuBackgroundColor(int colorResId) {
    backgroundColor = new Color(colorResId);
    paint.setColor(backgroundColor);
    invalidate();
  }

  private void setButtonPosition(float width) {
    if (buttonPosition == BUTTON_POSITION_CENTER) {
      button[LEFT] = ((width / 2) - (buttonSize / 2));
    } else if (buttonPosition == BUTTON_POSITION_LEFT) {
      button[LEFT] = 0;
    } else {
      button[LEFT] = width - buttonSize;
    }
    int padding = buttonMarginLeft - buttonMarginRight;
    button[LEFT] += padding;
    button[RIGHT] = button[LEFT] + buttonSize;
    button[TOP] = (height - buttonSize) / 2;
    button[BOTTOM] = (height + buttonSize) / 2;
  }

  /**
   * Set position of 'Open Menu' button.
   *
   * @param position One of: {@link #BUTTON_POSITION_CENTER}, {@link #BUTTON_POSITION_LEFT}, {@link #BUTTON_POSITION_RIGHT}.
   */
  public void setButtonPosition(int position) {
    buttonPosition = position;
    invalidate();
  }

  /**
   * Sets diameter of 'Open Menu' button.
   *
   * @param size Diameter in pixels.
   */
  public void setButtonSize(int size) {
    buttonSize = size;
    invalidate();
  }

  /**
   * Sets left margin for 'Open Menu' button.
   *
   * @param margin Left margin in pixels
   */
  public void setButtonMarginLeft(int margin) {
    buttonMarginLeft = margin;
  }

  /**
   * Sets right margin for 'Open Menu' button.
   *
   * @param margin Right margin in pixels
   */
  public void setButtonMarginRight(int margin) {
    buttonMarginRight = margin;
  }

  /**
   * Set anchor point of the menu. Can be either bottom or top.
   *
   * @param anchor One of: {@link #MENU_ANCHOR_BOTTOM}, {@link #MENU_ANCHOR_TOP}.
   */
  public void setAnchor(int anchor) {
    menuAnchor = anchor;
  }

  /**
   * Sets the passed drawable as the drawable to be used in the open state.
   *
   * @param openDrawable The open state drawable
   */
  public void setIconOpenDrawable(Element openDrawable) {
    this.iconOpenedDrawable = openDrawable;
    invalidate();
  }

  /**
   * Sets the passed drawable as the drawable to be used in the closed state.
   *
   * @param closeDrawable The closed state drawable
   */
  public void setIconCloseDrawable(Element closeDrawable) {
    this.iconClosedDrawable = closeDrawable;
    invalidate();
  }

  /**
   * Sets the passed drawable as the drawable to be used in the open state.
   *
   * @param openDrawable The open state drawable
   */
  public void setIconOpenedDrawable(Element openDrawable) {
    this.iconOpenedDrawable = openDrawable;
    invalidate();
  }

  /**
   * Sets the passed drawable as the drawable to be used in the closed state.
   *
   * @param closeDrawable The closed state drawable
   */
  public void setIconClosedDrawable(Element closeDrawable) {
    this.iconClosedDrawable = closeDrawable;
    invalidate();
  }

  @Override
  public void onComponentUnboundFromWindow(Component component) {

  }

  public void setClickedListener(ClickedListener onClickListener) {
    this.onClickListener = onClickListener;
  }

  @Override
  public void onDraw(Component component, Canvas canvas) {
    HiLog.info(TapBarMenu.label, "%{public}d", button[LEFT]);
    canvas.drawPath(createRoundedRectPath(path, button[LEFT], button[TOP], button[RIGHT], button[BOTTOM], button[RADIUS], button[RADIUS]), paint);

    canvas.rotate(360 * progress, getWidth() / 2, getHeight() / 2);
    if (state == State.CLOSED) {
      iconClosedDrawable.drawToCanvas(canvas);
    } else {
      iconOpenedDrawable.drawToCanvas(canvas);
    }
  }

  @Override
  public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
    if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
      if (onClickListener != null) {
        onClickListener.onClick(this);
      }
    }
    return true;
  }

  private void updateDimensions(float w, float h) {
    final int ratio = 5;
    width = w;
    height = h;

    button[RADIUS] = buttonSize;
    setButtonPosition(width);

    float iconLeft = button[LEFT] + buttonSize / ratio;

    float iconRight = button[RIGHT] - buttonSize / ratio;
    float iconBottom = (height + buttonSize) / 2 - buttonSize / ratio;
    float iconTop = (height - buttonSize) / 2 + buttonSize / ratio;
    iconOpenedDrawable.setBounds((int) iconLeft, (int) iconTop, (int) iconRight, (int) iconBottom);
    iconClosedDrawable.setBounds((int) iconLeft, (int) iconTop, (int) iconRight, (int) iconBottom);
  }

  private void showIcons(final boolean show) {
    for (int i = 0; i < getChildCount(); i++) {
      final Component view = getComponentAt(i);
      view.setScaleX(show ? 0f : 1f);
      view.setScaleY(show ? 0f : 1f);
      view.setVisibility(VISIBLE);
      view.setAlpha(show ? 0f : 1f);
      int translation = menuAnchor == MENU_ANCHOR_BOTTOM ? view.getHeight() : -view.getHeight();
      view.createAnimatorProperty()
          .moveFromY(show ? translation : 0f)
          .moveToY(0f)
          .scaleX(show ? 1f : 0f)
          .scaleY(show ? 1f : 0f)
          .alpha(show ? 1f : 0f)
          .setDuration(show ? animationDuration / 2 : animationDuration / 3)
          .setDelay(show ? animationDuration / 3 : 0)
          .setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
              view.setVisibility(show ? VISIBLE : HIDE);
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
          })
          .start();
    }
  }

  private Path createRoundedRectPath(Path path, float left, float top, float right, float bottom, float rx, float ry) {
    path.reset();
    path.addRoundRect(new RectFloat(left, top, right, bottom), rx, ry, Path.Direction.CLOCK_WISE);
    path.close();
    return path;
  }

  private enum State {
    OPENED,
    CLOSED
  }

}
